/* Copyright (C) 2011 rccloud.com All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.rccloud.gwtproxyc.server;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.rccloud.gwtproxyc.shared.DefaultProxyConverter;
import com.rccloud.gwtproxyc.shared.annotation.AutoProxy;
import com.rccloud.gwtproxyc.shared.annotation.AppEngineKey;
import com.rccloud.gwtproxyc.shared.annotation.NoProxy;

/**
 * Proxy info resolver, it's used for resolving GWT proxy class, it generates
 * the proxy info for each method for further use.
 */
class ProxyResolver {

	private Class<?> proxyClass;
	private Map<Field, FieldProxyInfo> fieldMap = new HashMap<Field, FieldProxyInfo>();
	private List<MethodProxyInfo> methodList = new ArrayList<MethodProxyInfo>();

	public ProxyResolver(Class<?> proxyClass) {
		this.proxyClass = proxyClass;
		if (this.proxyClass != null) {
			scanFieldsProxyInfo();
			scanMethodProxyInfo();
		}
	}

	/**
	 * Get annotation info of the method of a class
	 * 
	 * @return Proxy info list
	 */
	public List<MethodProxyInfo> resolveMethodProxyInfo() {
		return methodList;
	}

	/**
	 * Scan annotation info of the method
	 */
	private void scanMethodProxyInfo() {
		Set<Field> fields = fieldMap.keySet();
		for (Field field : fields) {
			FieldProxyInfo fieldProxy = fieldMap.get(field);

			Method setMethod = BeanUtils.getSetMethod(proxyClass, field);
			Method getMethod = BeanUtils.getGetMethod(proxyClass, field);
			Field targetField = BeanUtils.getField(fieldProxy.getTargetClass(),
					fieldProxy.getTargetFieldName(), field.getName());
			Method targetGetMethod = BeanUtils.getGetMethod(
					fieldProxy.getTargetClass(), targetField);
			Method targetSetMethod = BeanUtils.getSetMethod(
					fieldProxy.getTargetClass(), targetField);

			MethodProxyInfo methodProxy = new MethodProxyInfo();
			methodProxy.setProxyField(field);
			methodProxy.setSetMethod(setMethod);
			methodProxy.setGetMethod(getMethod);
			methodProxy.setTargetClass(fieldProxy.getTargetClass());
			methodProxy.setTargetGetMethod(targetGetMethod);
			methodProxy.setTargetSetMethod(targetSetMethod);
			methodProxy.setConverter(fieldProxy.getConverter());

			methodList.add(methodProxy);
		}
	}

	/**
	 * Scan annotation info of the field
	 */
	private void scanFieldsProxyInfo() {
		Class<?> defaultProxySourceClass = null;
		if (proxyClass.isAnnotationPresent(AutoProxy.class)) {
			defaultProxySourceClass = proxyClass.getAnnotation(AutoProxy.class)
					.value();
		}

		Field[] fields = BeanUtils.getAllFields(proxyClass, Object.class);
		for (Field field : fields) {

			FieldProxyInfo proxyInfo = new FieldProxyInfo();
			proxyInfo.setProxyField(field);
			proxyInfo.setTargetClass(defaultProxySourceClass);
			proxyInfo.setTargetFieldName(field.getName());
			proxyInfo.setConverter(DefaultProxyConverter.class);

			if (field.isAnnotationPresent(AutoProxy.class)) {

				AutoProxy autoProxy = field.getAnnotation(AutoProxy.class);
				if (autoProxy.value() != null)
					proxyInfo.setTargetClass(autoProxy.value());
				if (autoProxy.name().equals(""))
					proxyInfo.setTargetFieldName(autoProxy.name());
				proxyInfo.setConverter(autoProxy.converter());
			}
			if (field.isAnnotationPresent(AppEngineKey.class)) {
				proxyInfo.setConverter(AppEngineKeyConverter.class);
			}
			if (field.isAnnotationPresent(NoProxy.class)) {
				proxyInfo = null;
			}

			if (proxyInfo != null)
				fieldMap.put(field, proxyInfo);
		}
	}
}
